package Pattern;

import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Vector;

import Basics.*;
import Circuit.Circuit;
import Derivation.SemiNaive;
import Parsing.ParseDB;

public class IntersectWithProgramOffline 
{
	Pattern pattern;

	Circuit c;

	Map<Atom, HashSet<Body>> prov = new HashMap<Atom, HashSet<Body>>();

	public IntersectWithProgramOffline () {}
	
	
	public IntersectWithProgramOffline (PatternNode ipattern)
	{
		this.pattern = new Pattern(ipattern);
	}
	
	
	
	public IntersectWithProgramOffline (Vector<Vector<PatternNode>> patternVec)
	{
		this.pattern = new Pattern(patternVec);
	}
	
	
	
	public IntersectWithProgramOffline (Circuit ic, Pattern ipattern)
	{
		this.c = ic;
		this.pattern = ipattern;
	}
	
	
	
	public IntersectWithProgramOffline (Circuit ic, Vector<Vector<PatternNode>> patternVec)
	{
		this.c = ic;
		this.pattern = new Pattern(patternVec);
	}

	
	
	public IntersectWithProgramOffline (Map<Atom, HashSet<Body>> iprov, Pattern ipattern)
	{
		this.prov = iprov;
		this.pattern = ipattern;
	}
	
	//for debugging
	public IntersectWithProgramOffline (Map<Atom, HashSet<Body>> iprov, Circuit ic, Pattern ipattern)
	{
		this.c = ic;
		this.prov = iprov;
		this.pattern = ipattern;
	}
	

	public Circuit getC() 
	{
		return c;
	}



	public void setC(Circuit ic) 
	{
		this.c = ic;
	}
	
	
	
	public Pattern getPattern() 
	{
		return pattern;
	}



	public void setPattern(Pattern pattern) 
	{
		this.pattern = pattern;
	}
	
	
	
	/*************************************************************************************************************/
	/** Title: GetCircuitFromRules																				
	/** Description: Convert back to circuit from relevant rules				
	/*************************************************************************************************************/
	
	public void GetCircuitFromRules (Program prog, int sizeOfDB)
	{	
		//Program prog = GetRelevantProgram();
		DB.getInstance().Reset();
		DB.getInstance().DropTableIfExists("imports");
		DB.getInstance().DropTableIfExists("exports");
		DB.getInstance().DropTableIfExists("dealsWith");
		DB.getInstance().DropTableIfExists("dealsWith1");
		ParseDB rfDB = new ParseDB ("yago2core_facts.clean.notypes.tsv", sizeOfDB);
		/*Constant Cuba = new Constant("Cuba", "Country");
		Constant France = new Constant("France", "Country");
		Constant cigars = new Constant("cigars", "Product");

		Constant Mexico = new Constant("Mexico", "Country");

		Constant wine = new Constant("wine", "Product");
		Constant weapon = new Constant("weapon", "Product");
		Constant Israel = new Constant("Israel", "Country");
		Constant Palestine = new Constant("Palestine", "Country");
		Atom imPw = new Atom ("import", true, true, Palestine, weapon);
		Atom exIw = new Atom ("export", true, true, Israel, weapon);
		Atom exFw = new Atom ("export", true, true, France, wine);
		Atom exCc = new Atom ("export", true, true, Cuba, cigars);
		Atom imCw = new Atom ("import", true, true, Cuba, wine);
		Atom imMw = new Atom ("import", true, true, Mexico, wine);
		Atom imMc = new Atom ("import", true, true, Mexico, cigars);
		Atom imFc = new Atom ("import", true, true, France, cigars);
		
		
		DB.getInstance().Update(imPw);
		DB.getInstance().Update(exIw);
		DB.getInstance().Update(exFw);
		DB.getInstance().Update(exCc);
		DB.getInstance().Update(imCw);
		DB.getInstance().Update(imMw);
		DB.getInstance().Update(imMc);
		DB.getInstance().Update(imFc);*/
		Collections.sort(prog.getRules());
		if (false == prog.getRules().isEmpty()) 
		{
			SemiNaive sn = new SemiNaive(prog);
			sn.Run(false, true);
			this.c = sn.getParser().getC();
		}
		
		else
		{
			this.c = new Circuit();
		}
		/*for (Rule r : prog.getRules()) 
		{
			if (!r.isUsedAlready()) {
				System.out.println("GetCircuitFromRules:: problem!!");
			}
		}*/
		/*for (int i = 0; i < c.getCircuit().size(); i++) {
			if (c.getCircuit().get(i).size() != copy.getCircuit().get(i).size()) {
				System.out.println(String.format("GetCircuitFromRules:: layer %d isn't the same size", i));
			}
			
			for (Atom val : copy.getCircuit().get(i).values()) {
				if (!c.getCircuit().get(i+1).containsValue(val)) {
					System.out.println(String.format("GetCircuitFromRules:: layer %d of new circuit does not contain %s", i+1, val.toString()));
				}
			}
		}*/
		//System.out.println("new circuit: " + c.getCircuit());
		//System.out.println("after: " + this.c.getCircuit());
	}
	
	
	
	/*************************************************************************************************************/
	/** Title: AddToProv																				
	/** Description: 				
	/*************************************************************************************************************/
	
	public void AddToProv (Rule rule, int level)
	{
		if (false == this.prov.containsKey(rule.getHead()))
		{
			this.prov.put(rule.getHead(), new HashSet<Body>());
		}
		
		rule.getBody().setDerivedInlevel(rule.getDerivedInlevel());
		rule.getBody().setRuleUsed(rule.getHead().getRuleUsed());
		this.prov.get(rule.getHead()).add(rule.getBody());
		//rule.getHead().AddLevel(level);
		rule.getHead().setRelevantForDerivationTopDown(false);
	}
	
	
	
	/*************************************************************************************************************/
	/** Title: FromProgToProv																				
	/** Description: 				
	/*************************************************************************************************************/
	
	public void FromProgToProv (Program prog)
	{
		for (Rule rule : prog.getRules()) 
		{
			AddToProv(rule, rule.getDerivedInlevel());
		}
		
		if (prog.getRules().size() > 100000) 
		{
			prog.getRules().clear();;
		}
	}
	
	
	/*************************************************************************************************************/
	/** Title: FromProvToProg																				
	/** Description: 				
	/*************************************************************************************************************/
	
	public Program FromProvToProg ()
	{
		Program prog = new Program();
		for (Atom key : this.prov.keySet())
		{
			for (Body body : this.prov.get(key)) 
			{
				Rule temp = new Rule (key, body.getRuleUsed().getWeight(), body);
				temp.setDerivedInlevel(body.getDerivedInlevel());
				temp.getHead().setRuleUsed(body.getRuleUsed());
				prog.addRule(temp);
			}
		}
		
		if (this.prov.size() > 6000) 
		{
			this.prov.clear();
		}
		return prog;
	}
	
	
	
	/*************************************************************************************************************/
	/** Title: MarkRelevantProvPerKey																				
	/** Description: Marks all atoms that derive this root		
	/*************************************************************************************************************/
	
	public void MarkRelevantProvPerKey (List<Atom> queue, Map<String, Atom> keyMap)
	{
		Atom toMark = queue.remove(0);
		toMark.setRelevantForDerivationTopDown(true);
		if (keyMap.containsKey(toMark.toString())) 
		{
			if (false == toMark.isFact() && false == keyMap.get(toMark.toString()).isRelevantForDerivationTopDown())
			{
				Atom key = keyMap.get(toMark.toString());
				key.setRelevantForDerivationTopDown(true);
				for (Body body : this.prov.get(key)) 
				{
					for (Atom atom : body.getAtoms()) 
					{
						if (false == atom.isRelevantForDerivationTopDown()) 
						{
							queue.add(atom);
						}
					}
				}
			}
		}
	}
	
	
	
	/*************************************************************************************************************/
	/** Title: MarkRelevantProv																				
	/** Description: removes all atoms that aren't relevant for the derivation tree of this fact		
	/*************************************************************************************************************/
	
	public void MarkRelevantProv (Atom root)
	{		
		List<Atom> queue = new ArrayList<Atom>();
		queue.add(root);
		
		Map<String, Atom> keyMap = new HashMap<String, Atom>(); 
		for (Atom key : this.prov.keySet()) //insert all keys to a hash map for easy search 
		{
			keyMap.put(key.toString(), key);
		}
		
		while (false == queue.isEmpty())
		{
			MarkRelevantProvPerKey(queue, keyMap);
		}
	}
	
	
	
	/******************* ******************************************************************************************/
	/** Title: LeaveOnlyRelevantDerivations																				
	/** Description: clears the final prov. vector from atoms that are not used in the derivation of root		
	/*************************************************************************************************************/
	
	public void LeaveOnlyRelevantDerivations ()
	{
		List<Atom> toBeRemoved = new ArrayList<Atom>();
		for (Atom key : this.prov.keySet()) 
		{
			if (false == key.isRelevantForDerivationTopDown()) 
			{
				toBeRemoved.add(key);
			}
		}
		
		for (Atom atom : toBeRemoved) 
		{
			this.prov.remove(atom);
		}
	}
	
	
	
	/*************************************************************************************************************/
	/** Title: IntersectNoTransitives																				
	/** Description: Intersect pattern With Program no transitive children  			
	/*************************************************************************************************************/
	
	public void IntersectNoTransitives (int sizeOfDB)
	{
		System.out.println("starting offline intesection...");
		long startTime = System.currentTimeMillis();
		Program prog = FromProvToProg();
		long end = System.currentTimeMillis();
		/*if ((end-startTime) > 1000) 
		{
			System.out.println("time for FromProvToProg(): " + (end-startTime));
			//System.out.println("num of added inst atoms: " + this.programDeriver.getAddedInstAtomsInLastIteration().size());
		}*/
			//System.out.println("size of prog: " + prog.getRules().size());
		//startTime = System.currentTimeMillis();
		IntersectWithProgramOnline onlineIntersect = new IntersectWithProgramOnline(prog, this.pattern);
		onlineIntersect.IntersectNoTransitives ();
		//end = System.currentTimeMillis();
		/*if ((end-startTime) > 1000) 
		{*/
			//System.out.println("time for IntersectNoTransitives(): " + (end-startTime));
			//System.out.println("num of added inst atoms: " + this.programDeriver.getAddedInstAtomsInLastIteration().size());
		//}
		//startTime = System.currentTimeMillis();
		FromProgToProv(onlineIntersect.getP());
		//end = System.currentTimeMillis();
		/*if ((end-startTime) > 1000) 
		{
			System.out.println("time for FromProgToProv(): " + (end-startTime));
			//System.out.println("num of added inst atoms: " + this.programDeriver.getAddedInstAtomsInLastIteration().size());
		}*/
		
		PatternNode root = onlineIntersect.getPattern().getPatternVec().firstElement().firstElement();
		root.setName(root.getNewName());
		//startTime = System.currentTimeMillis();
		MarkRelevantProv(root);
		/*end = System.currentTimeMillis();
		if ((end-startTime) > 1000) 
		{
			System.out.println("time to mark relevant atoms: " + (end-startTime));
			//System.out.println("num of added inst atoms: " + this.programDeriver.getAddedInstAtomsInLastIteration().size());
		}*/
		//startTime = System.currentTimeMillis();
		LeaveOnlyRelevantDerivations();
		System.out.println("size of intersected hash: " + GetProvSize ());
		//end = System.currentTimeMillis();
		//startTime = System.currentTimeMillis();
		prog = FromProvToProg();
		end = System.currentTimeMillis();
		if ((end-startTime) > 1000) 
		{
			System.out.println("time to generate intersected prov.: " + (end-startTime));
			///System.out.println("num of added inst atoms: " + this.programDeriver.getAddedInstAtomsInLastIteration().size());
		}
		/*try
		{
			startTime = System.currentTimeMillis();
			prog = FromProvToProg();
			FileWriter fw = new FileWriter("rules_offline.csv");
			CSVWriter writer = new CSVWriter(fw);
			for (Rule rule : prog.getRules()) 
			{
				writer.writeNext(rule.toString());
				writer.flush();
			}
		}
		catch (Exception e)
		{
			System.out.println("error");
		}*/
		
		/*end = System.currentTimeMillis();
		if ((end-startTime) > 1000) 
		{*/
			//System.out.println("time for FromProvToProg() #2: " + (end-startTime));
			//System.out.println("num of added inst atoms: " + this.programDeriver.getAddedInstAtomsInLastIteration().size());
		//}
		
		//System.out.println("size of relevant prog: " + prog.getRules().size());
		
		
		startTime = System.currentTimeMillis();
		GetCircuitFromRules(prog, sizeOfDB);
		end = System.currentTimeMillis();
		System.out.println("time for seminaive: " + (end-startTime));
	}
	
	
	/*************************************************************************************************************/
	/** Title: GetProvSize																				
	/** Description: 				
	/*************************************************************************************************************/
	
	public int GetProvSize ()
	{
		int size = 0;
		for (HashSet<Body> set : this.prov.values()) 
		{
			size += set.size();
		}
		
		return size;
	}
}
